import path from 'path';
import Koa from 'koa';
import crypto from 'crypto';
import chalk from 'chalk';
import { IProxyOption, IKeyValue, InitTypeEnum, ProxyTypeEnum } from '../../models';
import { BaseService } from '../../configs/server.config';
import { FileUtil } from '../../utils/file.util';

export abstract class BaseProxy {

    proxyOption: IProxyOption;
    initType: InitTypeEnum;
    baseCookie: string;
    externalApi: (string | RegExp)[] = ['/menu/left_nav', '/menu/authorization', '/menu/dashboard/lastsrv', '/ecs/api/keystone'];

    constructor(option: IProxyOption) {
        this.proxyOption = option;
        const { initType: baseInitType, cookie: baseCookie } = BaseService.getConfig();
        this.initType = option.initType || baseInitType;
        this.baseCookie = option.cookie || baseCookie;
        if (option.externalApi) {
            this.externalApi = option.externalApi;
        }
    }

    private getLogPath(): string {
        const basePath = BaseService.getLogBasePath();
        return path.join(basePath, this.proxyOption.projectName || this.constructor.name);
    }

    getLogFilePath(ctx: Koa.Context) {
        const basePath = this.getLogPath();
        let filePath = '';
        if (ctx.header.referer) {
            const urlMd5 = crypto.createHash('md5').update(ctx.request.url).digest('hex');
            let referer = ctx.header.referer.replace(/^http(s)?:\/\/[\s\S]*?\//, '/');
            if (ctx.request.url.startsWith('/ems_dashboard_api')) {
                referer = encodeURIComponent(ctx.header?.host || '') + 'ems_dashboard_api';
            }
            const logPath = path.join(basePath, encodeURIComponent(referer));
            FileUtil.deepMakeDir(logPath);
            filePath = path.join(logPath, urlMd5);
        }
        // file name = md5(url) + method
        return filePath + '_' + ctx.method;
    }

    getLocalData(ctx: Koa.Context) {
        const logFilePath = this.getLogFilePath(ctx);
        return FileUtil.readFile(logFilePath) || '[]'
    }

    getCommonHeaders(ctx: Koa.Context): IKeyValue {
        let headers: IKeyValue = {
            'content-type': ctx.header['content-type'],
            'x-requested-with': 'XMLHttpRequest',
            'x-csrftoken': ctx.header['x-csrftoken'],
            cookie: ctx.header.cookie
        };

        if (ctx.header['upgrade']) {
            headers = { ...ctx.header };
        }

        if (this.baseCookie) {
            headers.cookie = this.baseCookie;
        }
        return headers;
    }

    getRequestOption(ctx: Koa.Context & { request }) {
        const headers = this.getHeaders(ctx);
        return {
            url: this.proxyOption.target + ctx.request.url,
            method: ctx.req.method,
            body: ctx.request.body,
            headers
        };
    }

    allowExternal(data: string, ctx: Koa.Context) {
        try {
            const objectResult = JSON.parse(data || '{}');
            const logFilePath = this.getLogFilePath(ctx);
            const url = ctx.request.url;
            let override = {};
            if (url.includes('/menu/left_nav') && !Object.keys(objectResult).length) {
                override = { code: '404', message: ctx.request.url };
            } else if (url.includes('/menu/authorization') && !objectResult.authorized) {
                override = { "category": "eks-secure", "name_zh-cn": "\u5b89\u5168\u5bb9\u5668\u670d\u52a1", "expire_days": -1, "disable_due_days": null, "license_status": "valid", "authorized": true, "expire_date": -1, "license_type": "platform", "name_en": "Secure Container Service", "license_authorize": true, "id": "eks-managed" };
            } else if (url.includes('/menu/dashboard/lastsrv') && !objectResult.success) {
                override = { "code": 200, "data": null, "success": true, "error": "" };
            }
            if (Object.keys(override).length) {
                data = FileUtil.readFile(logFilePath) || JSON.stringify(override);
            }
        } catch {
            // TODO
        }
        return data;
    }

    getIsExternal(url: string): boolean {
        return this.externalApi.some(matchItem => {
            if (typeof matchItem === 'string') {
                matchItem = new RegExp(`${matchItem}[\\s\\S]*`);
            }
            return matchItem.test(url);
        });
    }

    abstract getHeaders(ctx: Koa.Context): IKeyValue;
    abstract getResponse(response: any, ctx: Koa.Context): string;
    abstract allowExternalApi(data: string, ctx: Koa.Context): string;

}

export class NormalRequest extends BaseProxy {

    constructor(option: IProxyOption) {
        super(option);
    }

    getHeaders(ctx: Koa.Context): IKeyValue {
        const headers = this.getCommonHeaders(ctx);
        return Object.assign(headers, {});
    }

    allowExternalApi(data: string, ctx: Koa.Context): string {
        return this.allowExternal(data, ctx);
    }

    getResponse(response: any, ctx: Koa.Context): string {
        let resultData = response.body;
        const { successCode = '200', codeField = 'code' } = this.proxyOption;
        let errorCode = successCode;
        try {
            errorCode = parseInt(JSON.parse(resultData || '{}')[codeField], 0);
        } catch {
            errorCode = 500;
        }
        const logFilePath = this.getLogFilePath(ctx);
        // compatible
        if (this.initType === InitTypeEnum.compatible) {
            const url = ctx.request.url;
            const isExternal = this.getIsExternal(url);
            if (isExternal) {
                resultData = this.allowExternalApi(resultData, ctx);
            } else if (response.statusCode >= 300 || response.statusCode < 300 && errorCode !== Number(successCode)) {
                console.log(chalk.red('[compatible]-->'), url, chalk.red(response.statusCode), resultData);
                resultData = FileUtil.readFile(logFilePath) || response.body || JSON.stringify({ code: '404', message: url });
            }
        }
        if (response.statusCode === 200 && errorCode === Number(successCode) || this.initType === InitTypeEnum.compatible) {
            FileUtil.writeFile(logFilePath, resultData);
        }
        return resultData;
    }
}

export class RestfulRequest extends BaseProxy {

    constructor(option: IProxyOption) {
        super(option);
    }

    getHeaders(ctx: Koa.Context): IKeyValue {
        const headers = this.getCommonHeaders(ctx);
        return Object.assign(headers, {});
    }

    allowExternalApi(data: string, ctx: Koa.Context): string {
        return this.allowExternal(data, ctx);
    }

    getResponse(response: any, ctx: Koa.Context): string {
        return response.body;
    }

}

export class ProxyFactory {

    constructor() {
    }

    static getProxy(option: IProxyOption): BaseProxy {
        const proxyType = option.proxyType || ProxyTypeEnum.NORMAL;
        if (proxyType === ProxyTypeEnum.NORMAL) {
            return new NormalRequest(option);
        } else if (proxyType === ProxyTypeEnum.RESTFUL) {
            return new RestfulRequest(option);
        }
    }
} 